<?php
/**
* php4 version for class databean.
* This file belongs to a webappkit package (kit)
* @link http://webappkit.net
* @package databeans
* @subpackage databean
* @author J.Ducastel <jeremie@ducastel.name>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

/**
* root class for any data structure : stores, checks and formats data as members.
*
* <p>searches for member-related methods for setting, getting,
* checking, input and output filtering etc of members.</p>
* <ul>
* <li>bool _{member}_check($x)</li>
* <li>mixed _{member}_get($x)</li>
* <li>mixed _{member}_set($x)</li>
* <li>bool _{member}_gettable()</li>
* <li>bool _{member}_settable()</li>
* <li>bool _{member}_required()</li>
* <li>mixed _{member}_inputfilter($x)</li>
* <li>mixed _{member}_outputfilter($x)</li>
* <li>mixed _{member}_autovalue()</li></ul>
* @author J.Ducastel <jeremie@ducastel.name>
*/
class DataBean {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	* constructor for php4
	* @access public
	* @param array $data associative array members keys => values
	*/
	function DataBean($data=NULL) {
		$this->__construct($data);
	}/**/

	/**
	* "real" constructor
	* @access public
	* @param array $data associative array members keys => values
	*/
	function __construct($data=NULL) {
		// setting auto values if not provided
		$this->autoValues();
		// setting provided members' values if valid
		if (is_array($data))
			$this->loadMembers($data);
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	 * sets exceptions/notice throwing, false by default
	 * @access public
	 * @param bool $newstate won't change if null
	 * @return bool new/current state
	 */
	function errors($newstate=null) {
		if (is_bool($newstate))
			$this->_databean_errors=$newstate;
		return $this->_databean_errors;
	}


	/**
	* locks / unlocks databean structure, provides lock status.
	* When structure is locked,
	* properties can't be set if not declared first.
	* @access public
	* @param bool $newstate optional, new lock state
	* @return bool
	*/
	function membersLock($newstate=null) {
		if (is_bool($newstate))
			$this->_databean_members_lock=$newstate;
		return $this->_databean_members_lock;
	}/**/
	/**
	* alias for membersLock()
	*/
	function structureLock($new=null) {
		return $this->membersLock($new);
	}

	/**
	* locks / unlocks databean data, provide lock status.
	* When values are locked, they can't be changed.
	* @access public
	* @param bool $newstate optional, new lock state
	* @return bool
	*/
	function valuesLock($newstate=null) {
		if (!is_null($newstate) and is_bool($newstate))
			$this->_databean_values_lock=$newstate;
		return $this->_databean_values_lock;
	}/**/

	/**
	* Enable/disable inputfilters, provide inputfilters status.
	* when true, members method _{member}_inputfilter($x) will be applied to member new value.
	* @access public
	* @param bool $newstate optional
	* @return bool
	*/
	function inputFilters($newstate=null) {
		if (is_bool($newstate))
			$this->_databean_inputfilters=$newstate;
		return $this->_databean_inputfilters;
	}/**/

	/**
	* enable/disable outputfilters, provide outputfilters status.
	* when true, members method _{member}_outputfilter($x) will be applied to member value's output.
	* @access public
	* @param bool $newstate optional
	* @return bool
	*/
	function outputFilters($newstate=null) {
		if (is_bool($newstate))
			$this->_databean_outputfilters=$newstate;
		return $this->_databean_outputfilters;
	}/**/

	/**
	* enable/disable check before setting members.
	* When true, bool _{member}_check($x) will be called (after input filter) before setting new value.
	* @access public
	* @param bool $newstate optional
	* @return bool
	*/
	function checkOnSet($newstate=null) {
		if (is_bool($newstate))
			$this->_databean_checkonset=$newstate;
		return $this->_databean_checkonset;
	}/**/

	/**
	* Tries to load an associative array as members.
	* @access public
	* @param array $data
	* @return int number of successfully loaded members
	*/
	function loadMembers($data) {
		$loaded=0;
		if ($this->valuesLock() or !is_array($data))
			return 0;
		foreach (array_keys($data) as $key) {
			if ($this->setMember($key,$data[$key])) $loaded++;
		}
		return $loaded;
	}/**/
	/**
	* alias for loadmembers()
	*/
	function loadData($data) {
		return $this->loadMembers($data);
	}/**/

	/**
	 * loads data from POST. Avoids magic quotes backslashing
	 * @access public
	 * @param mixed $data
	 * if null, imports $_POST.
	 * If string, imports $_POST[$data].
	 * If array, imports given data
	 * @return int number of successfully loaded members
	 */
	function loadPostData($data=null) {
		switch (true) {
			case is_null($data):
				$data=$_POST;
				break;
			case is_string($data) and isset($_POST[$data]):
				$data=$_POST[$data];
				break;
			default:
				return 0;
		}
		// cleaning backslashes
		if (get_magic_quotes_gpc()) {
			foreach (array_keys($data) as $key) {
				$data[$key]=stripslashes($data[$key]);
			}
		}
		return $this->loadMembers($data);
	}

	/**
	 * sets default values for members.
	 * imports default values for members
	 * and/or applies defaults for null members
	 * @access public
	 * @param array $data
	 * @return int successfully set member's count
	 */
	function loadDefaults($data=array()) {
		if (!is_array($data)) {
			$this->throwError("databean->loaDefaults(array): was not an array");
			return 0;
		}
		$count=0;
		foreach (array_keys($data) as $key) {
			if ($this->setMemberDefault($key,$data[$key]))
				$count++;
		}
		return $count;
	}/**/

	/**
	* Return a member's value.
	* <ol>
	* <li>checks memberGettable(), may abort if false</li>
	* <li>returns _{member}_get() if method exists</li>
	* <li>gets current value or null if member don't exist</li>
	* <li>returns memberOutputFilter()</li>
	* </ol>
	* Output filter method _{member}_outputfilter($x) may be applied before.
	* @param string $key
	* @access public
	* @return mixed
	*/
	function getMember($key) {
		// checking get is allowed
		if (!$this->memberGettable($key)) {
			$this->_throwError("databean->get() not allowed for $key");
			return null;
		}
		// searching for custom method
		$method='_'.$key.'_get';
		if (method_exists($this,$method)) {
			return $this->$method();
		}
		// getting member's value
		$value=isset($this->$key)?$this->$key:null;
		// output filtering
		if ($this->outputFilters())
			return $this->memberOutputFilter($key,$value);
		return $value;
	}/**/
	/**
	* alias for getMember()
	*/
	function get($key) {
		return $this->getMember($key);
	}/**/

	/**
	* lists members. return members key/name in array
	* @access public
	* @return array
	*/
	function listMembers() {
		return $this->_getMembers();
	}/**/

	/**
	 * lists required members
	 * @access public
	 * @return array
	 */
	function listRequiredMembers() {
		$all=$this->listMembers();
		$required=array();
		foreach ($all as $key) {
			$method='_'.$key.'_required';
			if (method_exists($this,$method) and $this->$method())
				$required[]=$key;
		}
		return $required;
	}/**/

	/**
	* Indicates wether member is gettable.
	* search for custom bool _{key}_gettable() method
	* then checks if member exists or have a _{key}_set() generator
	* @access public
	* @param string $key
	* @return bool
	*/
	function memberGettable($key) {
		// searching for member's custom method
		$method='_'.$key.'_gettable';
		if (method_exists($this,$method)) {
			return $this->$method();
		}
		// searching for member or generator
		$method='_'.$key.'_get';
		if (!$this->hasMember($key) and !method_exists($this,$method))
			return false;
		return true;
	}/**/

	/**
	* Get all members as associative array.
	* @access public
	* @return array
	*/
	function & getAllAssoc() {
		$assoc=array();
		$keys=$this->_databean_getMembers();
		foreach ($keys as $key)
			$assoc[$key]=$this->getMember($key);
		return $assoc;
	}/**/
	/**
	* alias for getallassoc()
	*/
	function & getAll() {
		$assoc=&$this->getAllAssoc();
		return $assoc;
	}/**/

	/**
	* returns an html representation
	* @access public
	* @return string
	* /
	function toHtml() {
		$data=$this->getAllAssoc(); //print_r($data);
		$html='<dl>';
		foreach ($data as $key=>$value) {
			$html.='<dt>'.$key.'</dt>'
			.'<dd>'.gettype($value).' '.strval($value).'</dd>';
		}
		$html.='</dl>';
		return $html;
	}/**/

	/**
	* Set databean member's value, if checks are ok.
	* <ol>
	* <li>checks for membersLock(), may abort if true and member don't exists</li>
	* <li>checks for memberSettable(), may abort if false</li>
	* <li>search for custom setting method _{member}_set(). if found, return its result</li>
	* <li>applies _{member}_inputfilter() if exists</li>
	* <li>calls checkMember(), may abort if false</li>
	* <li>finally sets new value</li>
	* </ol>
	* @param string $key
	* @param mixed $value
	* @access public
	* @return bool
	*/
	function setMember($key,$value) {
		// checking values lock
		if ($this->valuesLock()) {
			$this->_throwError("databean->set(): values are locked");
			return false;
		}
		// checking set allowed
		if (!$this->memberSettable($key)) {
			$this->_throwError("databean->set() not allowed for $key");
			return false;
		}
		// searching for custom method
		$method='_'.$key.'_set';
		if (method_exists($this,$method)) {
			return $this->$method($value);
		}
		// applying input filter
		if ($this->inputFilters()) {
			$value=&$this->memberInputFilter($key,$value);
		}
		// checking validity
		if ($this->checkOnSet() and !$this->memberCheck($key,$value)) {
			$this->_throwError(get_class($this)."->setMember($key,$value) : value doesn't pass check");
			return false;
		}
		// finally setting value
		$this->$key=&$value;
		return true;
	}/**/
	/**
	* alias for setMember()
	*/
	function set($key,$value) {
		return $this->setMember($key,$value);
	}/**/

	/**
	 * set's member default : sets member only if null.
	 * @access public
	 * @param string $key
	 * @param mixed $value
	 * @return bool
	 */
	function setMemberDefault($key,$value) {
		if (is_null($this->$key))
			return $this->setMember($key,$value);
		else
			return false;
	}/**/

	/**
	* Indicates wether set is allowed for member.
	* <ol>
	* <li>search for _{member}_settable()</li>
	* <li>checks membersLock(). If true, member have to be declared before.</li>
	* </ol>
	* @access public
	* @param string $key member's name
	* @return bool
	*/
	function memberSettable($key) {
		// searching for member's custom "settable" method
		$method='_'.$key.'_settable';
		if (method_exists($this,$method)) {
			return $this->$method();
		}
		// disallow if values are locked
		if ($this->valuesLock()) {
			return false;
		}
		// disallow if structure is locked and is not declared property
		if ($this->membersLock() and !$this->hasMember($key)) {
			$this->_throwError($key.' is not a member of '.get_class($this).' and structure is locked');
			return false;
		}
		return true;
	}

	/**
	 * tells if bean has a member with this name
	 * @access public
	 * @param string $name
	 * @return bool
	 */
	function hasMember($name) {
		return in_array($name,$this->listMembers());
	}

	/**
	* Search for member's input filter and applies it to return value,  or return value.
	* input filter methods : _{member}_inputfilter($value).
	* @access public
	* @param string $key
	* @param mixed $value
	* @return mixed
	*/
	function & memberInputFilter($key,$value) {
		$this->_databean_callCustomMethod("_{$key}_inputfilter",$value);
		return $value;
	}/**/

	/**
	* search for member's input filter and applies it on value, or return value
	* output filter methods : _{member}_outputfilter($value).
	* @access public
	* @param string $key
	* @param mixed $value
	* @return mixed
	*/
	function & memberOutputFilter($key,$value) {
		$this->_databean_callCustomMethod("_{$key}_outputfilter",$value);
		return $value;
	}/**/

	/**
	* Checks a potential value for a member
	* @access public
	* @param string $key
	* @param mixed $value
	* @return bool
	*/
	function memberCheck($key, $value) {
		// custom checking methods
		$method="_{$key}_check";
		if (method_exists($this,$method)) {
			return $this->$method($value);
		}
		// no check defined
		return true;
	}/**/

	/**
	* checks bean global validity.
	* Actually checks every member flagged as required :
	* _{member}_required() returning true
	* @access public
	* @return bool
	*/
	function globalCheck() {
		return $this->_checkRequiredMembers();
	}

	/**
	* member's validity, or calls globalCheck if member name's not provided
	* @access public
	* @param string $member optional, member name
	* @return bool
	*/
	function isValid($member=null) {
		if (is_null($member))
			return $this->globalCheck();
		else
			return $this->memberCheck($member,$this->get($member));
	}/**/

	/**
	* Assign automatic values for fields.
	* searches for _{member}_autovalue() methods
	* @access public
	* @return bool
	*/
	function autoValues() {
		$members=$this->_databean_getMembers();
		foreach ($members as $key) {
			$method="_{$key}_autovalue";
			$property=$method;
			if (method_exists($this,$method)) {
				$this->$key=$this->$method();
			}/* else if (isset($this->$property)) {
				$this->$key=$this->$property;
			}*/
		}
		return true;
	}/**/

/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var bool enable/disable set() and loadData().
	* Changed by valuesLock()
	*/
	var $_databean_values_lock=false;
	/**
	* @var bool enable/disable setting undeclared members
	* changed by membersLock()
	*/
	var $_databean_members_lock=false;
	/**
	* @var bool enable/disable input filter methods on setMember()
	* changed by inputFilters()
	*/
	var $_databean_inputfilters=true;
	/**
	* @var bool enable/disable output filter methods on getMember()
	* changed by outputFilters()
	*/
	var $_databean_outputfilters=true;
	/**
	* @var bool if true, check member's validity before setting and don't set if invalid
	* changed by checkOnSet()
	*/
	var $_databean_checkonset=true;

	/**
	 * @var bool will trigger errors/exceptions if true
	 */
	var $_databean_errors=false;
	/**
	* @var bool sets _databean_lock_members=true after building
	*/
	//var $_databean_lock_members_afterbuilt=false;
	/**
	* @var bool sets _databean_lock_structure=true after building
	*/
	//var $_databean_lock_structure_afterbuilt=false;
/*------------------------------------------------------------------------------
                                                               protected methods
------------------------------------------------------------------------------*/

	/**
	* return bean's declared (public) members
	* i.e. all vars not starting by an underscore
	* @access protected
	* @return array
	*/
	function & _databean_getMembers() {
		// getting all instance vars
		//$class=get_class($this);
		$allvars=array_keys(get_object_vars($this));
		$members=array();
		foreach ($allvars as $var) {
			if (substr($var,0,1)!='_')
				$members[]=$var;
		}
		return $members;
	}/**/
	/**
	* alias for _databean_getMembers()
	*/
	function _getMembers() {
		return $this->_databean_getMembers();
	}

	/**
	* Checks validity for all members flagged as required
	* @access protected
	* @return bool
	*/
	function _databean_checkRequiredMembers() {
		$members=$this->_databean_getMembers();
		foreach ($members as $key) {
			$method='_'.$key.'_required';
			if (method_exists($this,$method)
			and $this->$method()
			and !$this->memberCheck($key,$this->$key))
				return false;
		}
		return true;
	}/**/
	/**
	* alias for _databean_checkrequiredmembers()
	*/
	function _checkRequiredMembers() {
		return $this->_databean_checkRequiredMembers();
	}/**/

	/**
	* Applies custom own method on value.
	* works by reference !
	* @param string $method
	* @param mixed &$value
	* @access protected
	* @return bool
	*/
	function _databean_callCustomMethod($method,&$value) {
		if (!method_exists($this,$method))
			return false;
		$value=$this->$method($value);
		return true;
	}/**/

	function _throwError($msg=null) {
		if ($this->errors()) {
			trigger_error($msg,E_USER_NOTICE);
		}
	}
}
?>